function Add-BootsFunction {
#
#.Synopsis
#   Add support for a new class to Boots by creating the dynamic constructor function(s).
#.Description
#   Creates a New-Namespace.Type function for each type passed in, as well as a short form "Type" alias.
#
#   Exposes all of the properties and events of the type as perameters to the function. 
#
#   NOTE: The Type MUST have a default parameterless constructor.
#.Parameter Type
#   The type you want to create a constructor function for.  It must have a default parameterless constructor.
#.Example
#   Add-BootsFunction ([System.Windows.Controls.Button])
#   
#   Creates a new boots function for the Button control.
#
#.Example
#   [Reflection.Assembly]::LoadWithPartialName( "PresentationFramework" ).GetTypes() | Add-BootsFunction
#
#   Will create boots functions for all the WPF components in the PresentationFramework assembly.  Note that you could also load that assembly using GetAssembly( "System.Windows.Controls.Button" ) or Load( "PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" )
#
#.Example
#   Add-BootsFunction -Assembly PresentationFramework
#
#   Will create boots functions for all the WPF components in the PresentationFramework assembly.
#
#.Links 
#   http://HuddledMasses.org/powerboots
#.ReturnValue
#   The name(s) of the function(s) created -- so you can export them, if necessary.
#.Notes
# AUTHOR:    Joel Bennett http://HuddledMasses.org
# LASTEDIT:  2009-01-13 16:35:23
#
## [CmdletBinding(DefaultParameterSetName="FromType")]
PARAM(
## [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="FromType",Mandatory=$true)]
   [type[]]$type
,
## [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="FromAssembly",Mandatory=$true)]
   [string[]]$Assembly
,
## [Parameter()]
   [switch]$Force
)
BEGIN {
   [Type[]]$Empty=@()
   if(!(Test-Path $PowerBootsPath\Functions)) {
      MkDir $PowerBootsPath\Functions
   }
}
PROCESS {
   if($Assembly) {
      [type[]]$type = @()
      foreach($lib in $Assembly) {
         $asm =  $null
         trap { continue }
         if(Test-Path $lib) {
            $asm =  [Reflection.Assembly]::LoadFrom( (Convert-Path (Resolve-Path $lib -EA "SilentlyContinue") -EA "SilentlyContinue") )
         }
         if(!$asm) {
            ## BUGBUG: LoadWithPartialName is "Obsolete" -- but it still works in 2.0/3.5
            $asm =  [Reflection.Assembly]::LoadWithPartialName( $lib )
         }
         if($asm) {
            $type += $asm.GetTypes() | ?{ $_.IsPublic    -and !$_.IsEnum      -and 
                                         !$_.IsAbstract  -and !$_.IsInterface -and 
                                         $_.GetConstructor( "Instance,Public", $Null, $Empty, @() )}
         } else {
            Write-Error "Can't find the assembly $lib, please check your spelling and try again"
         }
      }
   }

   $LoadedAssemblies = Get-BootsAssemblies 
   
   foreach($T in $type) {
      $TypeName = $T.FullName
      $ScriptPath = "$PowerBootsPath\Functions\New-$TypeName.ps1"
      Write-Verbose $TypeName

      ## Collect all dependency properties ....
      $T.GetFields() | 
         Where-Object { $_.FieldType -eq [System.Windows.DependencyProperty] } |
         Select-Object Name, @{n="DeclaringType";e={$_.DeclaringType.FullName}}, 
                  @{n="PropertyType";e={$_.DeclaringType::"$($_.Name)".PropertyType.FullName}},
                  @{n="Field";e={$_.DeclaringType::"$($_.Name)".Name}} |
         ForEach-Object { 
            if($DependencyProperties.ContainsKey( $_.Field )) {
               $DependencyProperties[$_.Field] = @($DependencyProperties[$_.Field]) + @($_)
            } else {
               $DependencyProperties[$_.Field] = $_
            }
         }
            
      Write-Verbose "Testing $ScriptPath   ($(Test-Path $ScriptPath))"
      if(!( Test-Path $ScriptPath ) -OR $Force) {
         $ContentProperty = ""
         $ContentPattern = "^$([string]::Join('$|^', $BootsContentProperties))`$"
         ## Get (or generate) a set of parameters based on the the Type Name
         $Parameters = [String]::Join("`n,`t", @(
            ## Add all properties
            foreach ($p in $T.GetProperties("Public,Instance,FlattenHierarchy") | 
                              where {$_.CanWrite -Or $_.PropertyType.GetInterface([System.Collections.IList]) } | Sort Name -Unique) {
               if($ContentProperty.Length -eq 0 -and $p.Name -match $ContentPattern) {
                  $ContentProperty = Add-Member -in $p.Name -Type NoteProperty -Name "IsCollection" -Value $($p.PropertyType.GetInterface([System.Collections.IList]) -ne $null) -Passthru
               } 
               elseif($p.PropertyType -eq [System.Boolean]) {
                  "[Switch]`$$($p.Name)"
               } 
               else {
                  "[Object[]]`$$($p.Name)"
               }
            }
            
            ## Add all events
            foreach ($e in $T.GetEvents("Public,Instance,FlattenHierarchy")) {
               "[PSObject]`$On_$($e.Name)"
            }
         ))

         if($ContentProperty) {
            $Parameters = "PARAM(`n[Object[]]`$$ContentProperty`n,`t$Parameters`n)"
         } else {
            $Parameters = "PARAM(`n$Parameters`n)"
         }
         $ofs = "`n";

         #  Write-Host "Content Property for $TypeName: $($ContentProperty -ne $Null)" -Fore Cyan
         #  foreach($p in $ContentProperty) {write-host "$p is $(if(!$p.IsCollection) { "not " })a collection"}

### These three are "built in" to boots, so we don't need to write preloading for them
# PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
# WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
# PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

$loadStatement = &{ 
   $index = [Array]::BinarySearch($LoadedAssemblies, $T.Assembly.FullName)
   
   if( $index -gt 0 -and $LoadedAssemblies[$index].Location ) {
"  `$null = [Reflection.Assembly]::LoadFrom( '" + $LoadedAssemblies[$index].Location + "' ) "
   } else {
"  `$null = [Reflection.Assembly]::Load( '" + $($T.Assembly.FullName) + "' ) "
   }
}

$function = $(
"
$Parameters
#### function Global:New-$TypeName {
#
#.Synopsis
#   Create a new $($T.Name) object
#.Description
#   Generates a new $TypeName object, and allows setting all of it's properties
#.Notes
# AUTHOR:    Joel Bennett http://HuddledMasses.org
# LASTEDIT:  $(Get-Date)
 

BEGIN {
   ## Preload the assembly if it's not already loaded
   if( [Array]::BinarySearch(@(Get-BootsAssemblies), '$($T.Assembly.FullName)' ) -lt 0 ) {
   $loadStatement
   }

   `$DObject = New-Object $TypeName
   `$All = Get-ChildItem Variable: | Where-Object {
      @(`$_.Attributes | ForEach-Object { `$_ -is [System.Management.Automation.ParameterAttribute] }) -contains `$true 
   } | ForEach-Object { `$_.Name } | Sort-Object

}
PROCESS {
"
if(!$ContentProperty) {
"
   # The content of $TypeName is not a collection
   # So if we're in a pipeline, make a new $($T.Name) each time
"
}
"
   if(`$_) {"
if($ContentProperty){
   if(!($ContentProperty.IsCollection)) {
"
      `$DObject = New-Object $TypeName"
   }
"
      `$$ContentProperty = `$_
"
}
"
   }
"

@'

   $Parameters = Get-Variable -Scope Local | Where-Object { 
      $_.Value -ne $null -and 
      @( $_.Attributes | 
         ForEach-Object { $_ -is [System.Management.Automation.ParameterAttribute] }
      ) -contains $true 
   }
   
   foreach ($param in $Parameters) {
      ## HANDLE EVENTS ....
      if ($param.Name.StartsWith("On_")) 
      {
         $EventName = "Add_" + $param.Name.SubString(3)
         $sb = $param.Value -as [ScriptBlock]
         if(!$sb) {
            $sb = (Get-Command $param.Value -CommandType Function,ExternalScript).ScriptBlock
         }
         Invoke-Expression "`$DObject.$EventName( {$sb} )"
      } else { ## HANDLE PROPERTIES .... 
         trap {
            Write-Host "COUGHT AN EXCEPTION" -fore Red
            Write-Host $($_ |fl * | out-String) -fore Red
            continue
         }

         ## TODO: File a BUG because Write-DEBUG and Write-VERBOSE die here.
         if($DebugPreference -ne "SilentlyContinue") {
            Write-Host "Setting $($param.Name) of $($DObject.GetType().Name) to $($param.Value)" -fore Yellow
         }
         if(@(foreach($sb in $param.Value) { $sb -is [ScriptBlock] }) -contains $true) {
            $Values = @()
            foreach($sb in $param.Value) {
               $Values += &$sb
            }
         } else {
            $Values = $param.Value
         }
   
         if ($DObject.$($param.Name) -is [System.Collections.IList]) {
            if($DebugPreference -ne "SilentlyContinue") { Write-Host "Parameter $($param.Name) is an IList" -fore Cyan}
            foreach ($value in @($Values)) {
               trap { # Write-Host "CAUGHT array problem" -fore Red
                  if($_.Exception.Message -match "Invalid cast from 'System.String' to 'System.Windows.UIElement'.") {
                     $null = $DObject.$($param.Name).Add( (TextBlock $value) )
                     continue
                  } else { 
                    throw $_
                  }
               }
               $null = $DObject.$($param.Name).Add($value)
            }
         } else {
            ## If they pass an array of 1 when we only want one, we just use the first value
            if($Values -is [System.Collections.IList] -and $Values.Count -eq 1) {
               if($DebugPreference -ne "SilentlyContinue") { Write-Host "Value is an IList" -fore Cyan}
               trap {
                  # Write-Host "CAUGHT collection value problem" -fore Red
                  if($_.Exception.Message -match "Invalid cast from 'System.String' to 'System.Windows.UIElement'.") {
                     $null = $DObject.$($param.Name).Add( (TextBlock $Values[0]) )
                     continue
                  } else { 
                     throw $_
                  }
               }
               $DObject.$($param.Name) = $Values[0]
            } else { ## If they pass an array when we only want one, we try to use it, and failing that, cast it to strings
               if($DebugPreference -ne "SilentlyContinue") { Write-Host "Value is a just $Values" -fore Cyan}
               trap {
                  # Write-Host "CAUGHT value problem" -fore Red
                  if($_.Exception.Message -match "Invalid cast from 'System.String' to 'System.Windows.UIElement'.") {
                     $null = $DObject.$($param.Name).Add( (TextBlock $values) )
                     continue
                  } else { 
                     throw $_
                  }
               }
               $DObject.$($param.Name) = $Values
            }
         }
         if($DebugPreference -ne "SilentlyContinue") {
            if( $DObject.$($param.Name) -ne $null ) {
               Write-Host $DObject.$($param.Name).GetType().FullName -fore Green
            }
         }
      }

#      if($Args) { Write-Host "Cannot process dependency properties yet:`n$($args|out-string)" -Fore Red }
      while($Args) {
         $name, $value, $Args = $Args
         $name = ([string]@($name)[0]).Trim("-")
         if($name -and $value) {
            Set-DependencyProperty -Element $DObject -Property $name -Value $Value
         }
      }
   }
'@
   if(!$ContentProperty -or !$ContentProperty.IsCollection) {
@'
   Microsoft.PowerShell.Utility\Write-Output $DObject
} #Process
'@
   } else {
@'
} #Process
END {
   Microsoft.PowerShell.Utility\Write-Output $DObject
}
'@
   }
@"
####}
"@
)

         Write-Verbose "Creating Script: $ScriptPath"
         Set-Content -Path $ScriptPath -Value $Function
      }
      New-Alias -Name $T.Name "New-$TypeName" -EA "SilentlyContinue"
   }                                                         
}#PROCESS
}#Add-BootsFunction
# SIG # Begin signature block
# MIILCQYJKoZIhvcNAQcCoIIK+jCCCvYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQULYn6rsaOHI5kwqoWvS9h5zZZ
# kEugggbgMIIG3DCCBMSgAwIBAgIJALPpqDj9wp7xMA0GCSqGSIb3DQEBBQUAMIHj
# MQswCQYDVQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxEjAQBgNVBAcTCVJvY2hl
# c3RlcjEhMB8GA1UEChMYaHR0cDovL0h1ZGRsZWRNYXNzZXMub3JnMSgwJgYDVQQL
# Ex9TY3JpcHRpbmcgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MTcwNQYDVQQDEy5odHRw
# Oi8vSHVkZGxlZE1hc3Nlcy5vcmcgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MScwJQYJ
# KoZIhvcNAQkBFhhKYXlrdWxASHVkZGxlZE1hc3Nlcy5vcmcwHhcNMDkwMzE1MTkx
# OTE5WhcNMTAwMzE1MTkxOTE5WjCBqzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5l
# dyBZb3JrMRIwEAYDVQQHEwlSb2NoZXN0ZXIxITAfBgNVBAoTGGh0dHA6Ly9IdWRk
# bGVkTWFzc2VzLm9yZzESMBAGA1UECxMJU2NyaXB0aW5nMRUwEwYDVQQDEwxKb2Vs
# IEJlbm5ldHQxJzAlBgkqhkiG9w0BCQEWGEpheWt1bEBIdWRkbGVkTWFzc2VzLm9y
# ZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPfqxOG9TQN+qZjZ6KfM
# +zBK0YpjeyPL/cFgiGBhiIdYWTBtkbZydFr3IiERKRsUJ0/SKFbhf0C3Bvd/neTJ
# qiZjH4D6xkrfdLlWMmmSXXqjSt48jZp+zfCAIaF8K84e9//7lMicdVFE6VcgoATZ
# /eMKQky4JvphJpzDHYPLxLJQrKd0pjDDwspjdX5RedWkzeZBG7VfBnebLWUzgnMX
# IxRQKfFCMryQDP8weceOnJjfJEf2FYmdpsEg5EKKKbuHsQCMVTxfteKdPvh1oh05
# 1GWyPsvEPh4auJUT8pAVvrdxq+/O9KW/UV01UxjRYM1vdklNw8g7mkJTrrHjSjl7
# tuugCnJjt5kN6v/OaUtRRMR68O85bSTVGOxJGCHUKlyuuTx9tnfIgy4siFYX1Ve8
# xwaAdN3haTon3UkWzncHOq3reCIVF0luwRZu7u+TnOAnz2BRlt+rcT0O73GN20Fx
# gyN2f5VGBbw1KuS7T8XZ0TFCspUdgwAcmTGuEVJKGhVcGAvNlLx+KPc5dba4qEfs
# VZ0MssC2rALC1z61qWuucb5psHYhuD2tw1SrztywuxihIirZD+1+yKE4LsjkM1zG
# fQwDO/DQJwkdByjfB2I64p6mk36OlZAFxVfRBpXSCzdzbgKpuPsbtjkb5lGvKjE1
# JFVls1SHLJ9q80jHz6yW7juBAgMBAAGjgcgwgcUwHQYDVR0OBBYEFO0wLZyg+qGH
# Z4WO8ucEGNIdU1T9MB8GA1UdIwQYMBaAFN2N42ZweJLF1mz0j70TMxePMcUHMAkG
# A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgTwMCoGA1UdJQEB/wQgMB4GCCsGAQUF
# BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMwCwYDVR0PBAQDAgTwMCwGCWCGSAGG+EIB
# DQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQUF
# AAOCAgEAmKihxd6KYamLG0YLvs/unUTVJ+NW3jZP16R28PpmidY/kaBFOPhYyMl2
# bBGQABe7LA5rpHFAs0F56gYETNoFk0qREVvaoz9u18VfLb0Uwqtnq0P68L4c7p2q
# V3nKmWjeI6H7BAyFuogxmMH5TGDfiqrrVSuh1LtPbkV2Wtto0SAxP0Ndyts2J8Ha
# vu/2rt0Ic5AkyD+RblFPtzkCC/MLVwSNAiDSKGRPRrLaiGxntEzR59GRyf2vwhGg
# oAXUqcJ/CVeHCP6qdSTM39Ut3RmMZHXz5qY8bvLgNYL6MtcJAx+EeUhW497alzm1
# jInXdbikIh0d/peTSDyLbjS8CPFFtS6Z56TDGMf+ouTpEA16otcWIPA8Zfjq+7n7
# iBHjeuy7ONoJ2VDNgqn9B+ft8UWRwnJbyB85T83OAGf4vyhCPz3Kg8kWxY30Bhnp
# Fayc6zQKCpn5o5T0/a0BBHwAyMfr7Lhav+61GpzzG1KfAw58N2GV8KCPKNEd3Zdz
# y07aJadroVkW5R+35mSafKRJp5pz20GDRwZQllqGH1Y/UJFEiI0Bme9ecbl2vzNp
# JjHyl/jLVzNVrBI5Zwb0lCLsykApgNY0yrwEqaiqwcxq5nkXFDhDPQvbdulihSo0
# u33fJreCm2fFyGbTuvR61goSksAvLQhvijLAzcKqWKG+laOtYpAxggOTMIIDjwIB
# ATCB8TCB4zELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMRIwEAYDVQQH
# EwlSb2NoZXN0ZXIxITAfBgNVBAoTGGh0dHA6Ly9IdWRkbGVkTWFzc2VzLm9yZzEo
# MCYGA1UECxMfU2NyaXB0aW5nIENlcnRpZmljYXRlIEF1dGhvcml0eTE3MDUGA1UE
# AxMuaHR0cDovL0h1ZGRsZWRNYXNzZXMub3JnIENlcnRpZmljYXRlIEF1dGhvcml0
# eTEnMCUGCSqGSIb3DQEJARYYSmF5a3VsQEh1ZGRsZWRNYXNzZXMub3JnAgkAs+mo
# OP3CnvEwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJ
# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB
# gjcCARUwIwYJKoZIhvcNAQkEMRYEFNeKBIq1Uk4Qp9rSIIyjEyLAh3xmMA0GCSqG
# SIb3DQEBAQUABIICACvUzOPb1afTfEL0jpozGxXk4VEjIIho95YMHMAGO2W3BXNz
# 1EsDcQWzdsVM+9Bu9hRA0JKunMxHVLeXn0hstvNsOZ09m3UfGTORgudyR7EWKbKl
# Dw7Qy+F9MDb2xdTtQLi0bfPu0hFVn0ob+5ptg+cWdeSHdWE87g5mMvuhMpcxF6DJ
# 9meNIRTfYCbjg/OPZWLoePU+9XEK2RAcwY/Q2SnUS30TplsY9NU6emCdJQlAwi8L
# zophslyCGf/j7P/9Hp6wm+nZlYZGHLhm9pOXxzeGQsYBHEbDRchx8+9iEvB7eyq1
# RYhxeHe3af0A3uT1V6iRCfYO0jRVVteMpYQQHC6OOuH60rbwdjv4ZBca2KHC9pN8
# MsVAmxT2mqZxFEEN2kF51qUV08E8dbEk/ASoQD1V7H8oXiqf5X6pZKb2f2vClB3W
# O3I1hHERp2sxauCHc5sjI4+y1BUEYCnpf1d2X0CwLfa5qj6cNlFZ4QTr7IxUGVMq
# pJ+w6BUbzIUoO4/5pzjVBocY7abieQTaED6wzI78aBTbH81AmSP4jshfTm6E4j1K
# a5ikGURyWlA+1Kj1VssTYAK2Ew3rXzMgh1FxZoRXtHdAL6HYZxHdDE/Jbjhvtr8Q
# 4VWf7lw6F8dEWd3rPAu6UPJv7vxMDimQxA9BlGvzxULdS0X4VACTDN9nKQL2
# SIG # End signature block
